/*
    The NeonX Library

    Copyright (c) 2013 darkcat

    Code covered by the MIT License
*/

#pragma once

// nx...
#include "config/general.h"
// RefBase, RefCounter
#include "memory/refcounter.h"
// nx::copy
#include "algorithm/algorithm.h"
// nx_verify
#include "bugfix/assert.h"
// nx_typeof
#include "type/typeof.h"
// nx_no_point
#include "type/typedetect.h"

//////////////////////////////////////////////////////////////////////////
NX_BEG
//////////////////////////////////////////////////////////////////////////

/*
    smart point
*/

namespace private_
{
    template <typename T, class Alloc_ = NX_ALLOC>
    class Pointer_ : public RefBase<Alloc_>
    {
    protected:
        T* ptr_;

    public:
        Pointer_(void) : ptr_(NX_NULL)   {}

    public:
        void set(T* p)
        {
            if (p)
            {
                RefBase<Alloc_>::init();
                ptr_ = p;
            }
        }

        void set(const Pointer_& p)
        {
            ptr_ = (RefBase<Alloc_>::set(p) ? p.ptr_ : NX_NULL);
        }

        void release(void)
        {
            nx::free<Alloc_>(ptr_);
            ptr_ = NX_NULL;
        }

        Pointer_& swap(Pointer_& rhs)
        {
            RefBase<Alloc_>::swap(rhs);
            nx::swap(ptr_, rhs.ptr_);
            return *this;
        }

        T* get(void) const        { return ptr_; }

        T& operator* (void) const { return *get(); }
        T* operator->(void) const { return get(); }
    };

    template <class Alloc_>
    class Pointer_<void, Alloc_> : public RefBase<Alloc_>
    {
    protected:
        void*  ptr_;
        size_t size_;

    public:
        Pointer_(void) : ptr_(NX_NULL), size_(0) {}

    public:
        void set(size_t s)
        {
            if (s == 0) return;
            RefBase<Alloc_>::init();
            nx_verify(ptr_ = nx::alloc<Alloc_, void>(size_ = s));
        }

        void set(void* p, size_t s)
        {
            if (p && s)
            {
                RefBase<Alloc_>::init();
                ptr_  = p;
                size_ = s;
            }
        }

        void set(const Pointer_& p)
        {
            if (RefBase<Alloc_>::set(p))
            {
                ptr_  = p.ptr_;
                size_ = p.size_;
            }
            else
            {
                ptr_  = NX_NULL;
                size_ = 0;
            }
        }

        void release(void)
        {
            nx::free<Alloc_>(ptr_, size_);
            ptr_ = NX_NULL;
        }

        Pointer_& swap(Pointer_& rhs)
        {
            RefBase<Alloc_>::swap(rhs);
            nx::swap(ptr_ , rhs.ptr_);
            nx::swap(size_, rhs.size_);
            return *this;
        }

        void*  get(void) const  { return ptr_; }
        size_t size(void) const { return size_; }
    };
}

template <typename T, class Alloc_ = NX_ALLOC>
class pointer : public RefCounter<private_::Pointer_<T, Alloc_> >
{
    /*
        Make T like char or byte is dangerous,
        because pointer does not know how many elements in itself

        if you want cast a char* or byte* to a pointer,
        best to use pointer<void>::set(void*, size_t)
    */
public:
    typedef RefCounter<private_::Pointer_<T, Alloc_> > base_t;
    typedef T       type_t;
    typedef T*      point_t;
    typedef Alloc_  alloc_t;

public:
    pointer(void)             : base_t()             {}
    template <typename T_>
    pointer(T_ r)             : base_t(r)            {}
    template <typename T_, typename U>
    pointer(T_ r, U s)        : base_t(r, s)         {}
    pointer(const pointer& r) : base_t(*(base_t*)&r) {}
};

/*
    Special swap algorithm
*/

template <typename T>
void swap(pointer<T>& x, pointer<T>& y)
{
    x.swap(y);
}

/*
    cast pointer to another pointer type
*/

namespace private_
{
    template <typename T, typename U, class Alloc_>
    struct pointer_cast_
    {
        typedef T* ret_t;

        static ret_t _(pointer<U, Alloc_>& operand)
        {
            return static_cast<ret_t>(operand.get());
        }
    };

    template <typename T_, class A_, typename U, class Alloc_>
    struct pointer_cast_<pointer<T_, A_>, U, Alloc_>
    {
        typedef pointer<T_, A_> ret_t;

        static ret_t _(pointer<U, Alloc_>& operand)
        {
            typedef typename ret_t::type_t  type_t;
            typedef typename ret_t::alloc_t alloc_t;
            typedef typename ret_t::point_t point_t;

            point_t pd = nx::alloc<alloc_t, type_t>();
            nx::copy(*pd, *(point_t)operand.get());
            return pd;
        }
    };

    template <typename T_, class A_, class Alloc_>
    struct pointer_cast_<pointer<T_, A_>, void, Alloc_>
    {
        typedef pointer<T_, A_> ret_t;

        static ret_t _(pointer<void, Alloc_>& operand)
        {
            typedef typename ret_t::type_t  type_t;
            typedef typename ret_t::alloc_t alloc_t;
            typedef typename ret_t::point_t point_t;

            point_t pd = nx::alloc<alloc_t, type_t>();
            nx::byte* ps = (nx::byte*)operand.get();
            nx::copy(pd, ps, ps + nx_min(sizeof(type_t), operand.size()));
            return pd;
        }
    };

    template <class A_, typename U, class Alloc_>
    struct pointer_cast_<pointer<void, A_>, U, Alloc_>
    {
        typedef pointer<void, A_> ret_t;
        
        static ret_t _(pointer<U, Alloc_>& operand)
        {
            typedef typename ret_t::alloc_t alloc_t;
            typedef typename ret_t::point_t point_t;

            point_t pd = nx::alloc<alloc_t, void>(sizeof(U));
            nx::copy(pd, operand.get(), operand.get() + 1);
            return ret_t(pd, sizeof(U));
        }
    };

    template <class A_, class Alloc_>
    struct pointer_cast_<pointer<void, A_>, void, Alloc_>
    {
        typedef pointer<void, A_> ret_t;

        static ret_t _(pointer<void, Alloc_>& operand)
        {
            typedef typename ret_t::alloc_t alloc_t;
            typedef typename ret_t::point_t point_t;

            point_t pd = nx::alloc<alloc_t, void>(operand.size());
            nx::byte* ps = (nx::byte*)operand.get();
            nx::copy(pd, ps, ps + operand.size());
            return ret_t(pd, operand.size());
        }
    };

    template <typename T_, class A_>
    struct pointer_cast_<pointer<T_, A_>, T_, A_>
    {
        typedef pointer<T_, A_>& ret_t;

        static ret_t _(ret_t operand)
        {
            return operand;
        }
    };
}

template <typename T, typename U, class Alloc_>
typename private_::pointer_cast_<T, U, Alloc_>::ret_t 
    pointer_cast(pointer<U, Alloc_>& operand)
{
    return private_::pointer_cast_<T, U, Alloc_>::_(operand);
}

template <typename T, typename U, class Alloc_>
typename private_::pointer_cast_<T, U, Alloc_>::ret_t 
    pointer_cast(const pointer<U, Alloc_>& operand)
{
    return pointer_cast<T>(const_cast<pointer<U, Alloc_>&>(operand));
}

/*
    For getting a pointer automatically
*/

#define nx_auto_pointer(name, ...) \
    nx::pointer<nx_no_point(nx_typeof(__VA_ARGS__))> name((__VA_ARGS__))

//////////////////////////////////////////////////////////////////////////
NX_END
//////////////////////////////////////////////////////////////////////////
